package ru.niir.goodfil.ui;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import ru.niir.goodfil.db.*;
import ru.niir.goodfil.exceptions.CreateFailureException;
import ru.niir.goodfil.exceptions.RemoveFailureException;
import ru.niir.goodfil.exceptions.ValidationException;
import ru.niir.goodfil.ui.utils.PageIndex;
import ru.niir.goodfil.utils.filters.ClientsFilter;
import ru.niir.goodfil.utils.filters.GoodsFilter;
import ru.niir.goodfil.utils.filters.PersonalPricesFilter;
import ru.niir.goodfil.utils.validation.CreateCheckResult;
import ru.niir.goodfil.utils.validation.ValidationError;
import ru.niir.goodfil.utils.validation.ValidationResult;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.model.SelectItem;
import java.util.ArrayList;
import java.util.List;

/**
 * @author
 */
public class PersonalPricesBean extends PageBean {

    private static Logger logger = Logger.getLogger(PersonalPricesBean.class);

    /**
     * Название настройки, задающей размер страницы
     */
    private static final String PERSONALPRICES_PAGE_SIZE_KEY = ".personalPrices.page.size";
    /**
     * Название настройки, задающей отступ от текущей страницы влево для пэйджинатора
     */
    private static final String PERSONALPRICES_LEFT_PAGE_OFFSET_SIZE = ".personalPrices.page.left.offset";
    /**
     * Название настройки, задающей отступ от текущей страницы вправо для пэйджинатора
     */
    private static final String PERSONALPRICES_RIGHT_PAGE_OFFSET_SIZE = ".personalPrices.page.right.offset";

    /**
     * Количество страниц (с учетом фильтра)
     */
    private int pagesCount = -1;
    /**
     * Количество замещенных менеджеров (с учетом фильтра)
     */
    private int personalPricesCount = -1;
    /**
     * Размер страницы
     */
    private int pageSize = -1;
    /**
     * Отступ от текущей страницы влево для паджинатора (кол-во страниц, слева от текущей)
     */
    private int _leftPageOffset = -1;
    /**
     * Отступ от текущей страницы вправо для паджинатора (кол-во страниц, справа от текущей)
     */
    private int _rightPageOffset = -1;

    /**
     * Персональные цены
     */
    private PersonalPrice personalPrice = new PersonalPrice();
    /**
     * Идентификатор персональной цены
     */
    private long personalPriceId = -1;
    /**
     * Идентификатор цены
     */
    private long userId = -1;
    /**
     * Идентификатор товара
     */
    private long goodId = -1;
    /**
     * Цена
     */
    private String price = "";

    /***/
    private long createPriceGoodTypeFilter;
    /***/
    private String createPriceGoodCodeFilter;
    /***/
    private String createPriceClientNameFilter;

    /**
     * Получить (согласно настройкам) размер страницы (для пэйджинатора)
     *
     * @return размер страницы
     */
    public int getPageSize() {
        gf().createIfNotExists(PERSONALPRICES_PAGE_SIZE_KEY, 10);
        if (pageSize == -1) pageSize = gf().getInt(PERSONALPRICES_PAGE_SIZE_KEY);
        return pageSize;
    }

    /**
     * Получить (согласно настройкам) размер отступа влево (в страницах) для пэйджинатора
     *
     * @return размер отступа
     */
    public int getLeftPaggeOffset() {
        gf().createIfNotExists(PERSONALPRICES_LEFT_PAGE_OFFSET_SIZE, 5);
        if (_leftPageOffset == -1) _leftPageOffset = gf().getInt(PERSONALPRICES_LEFT_PAGE_OFFSET_SIZE);
        return _leftPageOffset;
    }

    /**
     * Возвращает (согласно настройкам) размер отступа вправо (в страницах) для пэйджинатора
     *
     * @return размер отступа
     */
    public int getRightPaggeOffset() {
        gf().createIfNotExists(PERSONALPRICES_RIGHT_PAGE_OFFSET_SIZE, 5);
        if (_rightPageOffset == -1) _rightPageOffset = gf().getInt(PERSONALPRICES_RIGHT_PAGE_OFFSET_SIZE);
        return _rightPageOffset;
    }

    /**
     * Получить количество персональных цен (с учетом фильтра)
     *
     * @return количество персональных цен
     */
    public int getPersonalPricesCount() {
        if (personalPricesCount == -1)
            personalPricesCount = gf().getPersonalPricesCount(getSessionBean().getPersonalPricesFilter());
        return personalPricesCount;
    }

    /**
     * Получить количество страниц (с учетом фильтра)
     *
     * @return количестов страниц
     */
    public int getPagesCount() {
        if (pagesCount == -1) {
            pagesCount = getPersonalPricesCount() / getPageSize() + 1;
        }
        return pagesCount;
    }

    /**
     * Перейти к началу (на первую страницу)
     */
    public void toBegin() {
        applyPage();
        setPage(1);
    }

    /**
     * Перейти к концу (на последнюю страницу)
     */
    public void toEnd() {
        applyPage();
        setPage(getPagesCount());
    }

    /**
     * Отступить на несколько страниц назад
     */
    public void longPrevPage() {
        applyPage();
        int p = getPage() - getLeftPaggeOffset() - 1;
        if (p < 1) p = 1;
        setPage(p);
    }

    /**
     * Отступить на несколько страниц вперед
     */
    public void longNextPage() {
        applyPage();
        int p = getPage() + getRightPaggeOffset() + 1;
        if (p > getPagesCount()) p = getPagesCount();
        setPage(p);
    }

    /**
     * Перейти на страницу вперед
     */
    public void nextPage() {
        applyPage();
        if (getPage() < getPagesCount()) setPage(getPage() + 1);
    }

    /**
     * Перейти на страницу назад
     */
    public void prevPage() {
        applyPage();
        if (getPage() > 1) setPage(getPage() - 1);
    }

    /**
     * Возвращает номер текущей страницы.
     *
     * @return номер страници
     */
    public int getPage() {
        return getSessionBean().getPersonalPricesPage();
    }

    /**
     * Устанавливает номер текущей страницы.
     *
     * @param page номер страницы
     */
    public void setPage(int page) {
        getSessionBean().setPersonalPricesPage(page);
    }

    /**
     * Применить текущую страницу
     */
    public void applyPage() {
        pagesCount = -1;
        personalPricesCount = -1;
    }

    /**
     * Получить номера страниц для отрисовки в пэйджинаторе.
     *
     * @return номера страниц
     */
    public List<PageIndex> getPageIndexes() {

        int minPage = getPage() - getLeftPaggeOffset();
        if (minPage < 1) minPage = 1;

        int maxPage = minPage + getLeftPaggeOffset() + getRightPaggeOffset();
        if (maxPage > getPagesCount()) maxPage = getPagesCount();

        List<PageIndex> result = new ArrayList<PageIndex>();
        for (int i = minPage; i <= maxPage; i++)
            result.add(new PageIndex(i, i == getPage()));

        return result;
    }

    /**
     * Применить фильтр
     */
    public void applyFilter() {
        setPage(1);
        personalPricesCount = -1;
        pagesCount = -1;
    }

    /**
     * Получить номер первой страницы, которую необходимо отрисовать в пэйджинаторе.
     *
     * @return номер первой страници
     */
    private int getPageStart() {
        int pageStart = getPage() * getPageSize() - getPageSize();
        if (pageStart < 0) pageStart = 0;
        return pageStart;
    }

    /**
     * Получить номер последней страницы, которую необходимо отрисовать в пэйджинаторе
     *
     * @return номер последней страници
     */
    private int getPageEnd() {
        int pageEnd = getPageStart() + getPageSize();
        if (pageEnd > getPersonalPricesCount()) pageEnd = getPersonalPricesCount();
        return pageEnd;
    }

    /**
     * Возвращает информацию о замещении.
     *
     * @return
     */
    public PersonalPrice getPersonalPrice() {

        if (personalPrice.getId() != personalPriceId) {
            if (personalPriceId == -1) personalPrice = new PersonalPrice();
            else {
                personalPrice = gf().getPersonalPriceById(personalPriceId);
                personalPrice.setGood(gf().getGoodById(personalPrice.getGoodId()));
            }
        }

        return personalPrice;
    }

    /**
     * Возвращает идентификатор персонального прйса.
     *
     * @return идентификатор
     */
    public long getPersonalPriceId() {
        return personalPriceId;
    }

    /**
     * Устанавливает идентификтор персонального прайса.
     *
     * @param personalPriceId идентификатор
     */
    public void setPersonalPriceId(long personalPriceId) {
        this.personalPriceId = personalPriceId;
    }

    /**
     * Возвращает идентификатор клиента.
     *
     * @return идентификатор
     */
    public long getUserId() {
        return userId;
    }

    /**
     * Устанавливает идентификатор клиента.
     *
     * @param userId идентификатор
     */
    public void setUserId(long userId) {
        this.userId = userId;
    }

    /**
     * Возвращает идентификатор товара.
     *
     * @return идентификатор
     */
    public long getGoodId() {
        return goodId;
    }

    /**
     * Устанавливает идентификатор товара.
     *
     * @param goodId идентификатор
     */
    public void setGoodId(long goodId) {
        this.goodId = goodId;
    }

    /**
     * Возвращает персональную цену для фильра.
     *
     * @return цена
     */
    public String getPrice() {
        return price;
    }

    /**
     * Устанавливает персональную цену для фильтра.
     *
     * @param price цена
     */
    public void setPrice(String price) {
        this.price = price;
    }

    /**
     * Получить перечень цен для отображения, с учетом фильтра и номера текущей страницы
     */
    public List<PersonalPrice> getPersonalPrices() {
        if(getSecurityBean().getIsRoleManager()) {
            getSessionBean().getPersonalPricesFilter().setManagerId(getSecurityBean().getCurrentUser().getId());
        }
        List<PersonalPrice> personalPrices = gf().getPersonalPrices(getPageStart(), getPageSize(), getSessionBean().getPersonalPricesFilter());
        for (PersonalPrice pp : personalPrices) {
            //  Получаем товар
            pp.setGood(gf().getGoodById(pp.getGoodId()));

            //  Получаем клиента
            pp.setClient(gf().getClientById(pp.getClientId()));

            //  Получаем группу товаров
            Good good = pp.getGood();
            good.setGoodType(gf().getGoodTypeById(good.getGoodTypeId()));

            //  Получаем группу клиента
//            Client client = pp.getClient();
//            client.setClientGroup(gf().getClientGroupById(client.getClientGroupId()));
        }
        return personalPrices;
    }

    /**
     * Возвращает список клиентов.
     *
     * @return список клиентов
     */
    public List<SelectItem> getUsersList() {
        List<SelectItem> result = new ArrayList<SelectItem>();
        List<User> users = gf().getUsersByRoleId(Role.CLIENT);
        result.add(new SelectItem(-1, "Не установлен"));
        for (User user : users) result.add(new SelectItem(user.getId(), user.getFio()));
        return result;
    }

    /**
     * Возвращает список товаров.
     *
     * @return список товаров
     */
    public List<SelectItem> getGoodsList() {
        List<SelectItem> result = new ArrayList<SelectItem>();
        List<Good> goods = gf().getGoods(0, Integer.MAX_VALUE, new GoodsFilter());
        result.add(new SelectItem(-1, "Не установлен"));
        for (Good good : goods) result.add(new SelectItem(good.getId(), good.getName()));
        return result;
    }

    /**
     * Возвращает
     *
     * @return
     */
    public List<SelectItem> getFilteredClients() {
        if (StringUtils.isEmpty(createPriceClientNameFilter)) return new ArrayList<SelectItem>();

        ClientsFilter filter = new ClientsFilter();
        filter.setName(createPriceClientNameFilter);

        List<Client> clients = gf().getClients(0, 20, filter);
        List<SelectItem> result = new ArrayList<SelectItem>();
        for (Client client : clients) result.add(new SelectItem(client.getId(), client.getCompanyName()));
        return result;
    }

    /**
     * Возвращает
     *
     * @return
     */
    public List<SelectItem> getFilteredGoods() {
        if (createPriceGoodTypeFilter == -1 && StringUtils.isEmpty(createPriceGoodCodeFilter))
            return new ArrayList<SelectItem>();

        GoodsFilter goodsFilter = new GoodsFilter();
        if (createPriceGoodTypeFilter != -1) goodsFilter.setGoodTypeId(createPriceGoodTypeFilter);
        if (!StringUtils.isEmpty(createPriceGoodCodeFilter)) goodsFilter.setCode(createPriceGoodCodeFilter);
        List<Good> goods = gf().getGoods(0, 20, goodsFilter);
        List<SelectItem> result = new ArrayList<SelectItem>();
        for (Good good : goods) result.add(new SelectItem(good.getId(), good.getCode() + " " + good.getName()));
        return result;

    }

    /**
     * Поиск товара.
     */
    public void searchGood() {
        //logger.debug("searchGood");
        if ((createPriceGoodTypeFilter == -1) && (StringUtils.isEmpty(createPriceGoodCodeFilter))) {
            facesError("Необходимо установить группу товаров или ввести значение в поле Наименование товара (не менее 3х символов)");
            return;
        }

        if (createPriceGoodCodeFilter.length() < 3 && createPriceGoodTypeFilter == -1) {
            facesError("Значение в поле Наименование товара должно быть длиннее 3х символов");
            return;
        }
    }

    /**
     * Поиск клиента.
     */
    public void searchClient() {
        //logger.debug("searchClient");
        if (StringUtils.isEmpty(createPriceClientNameFilter)) {
            facesError("Необходимо ввести значение в поле ФИО клиента (не менее 3х символов)");
            return;
        }

        if (createPriceClientNameFilter.length() < 3) {
            facesError("Значение в поле ФИО клиента должно быть длиннее 3х символов");
            return;
        }
    }


    /**
     * -------------------------------------------------------------------------
     * ----------Работа с БД----------
     * -------------------------------------------------------------------------
     */

    /**
     * Добавление новой персональной цены для панели addPersonalPricesPanelSafe
     */
    public void addPersonalPrice() {
        //
        //  Первичная валидация
        //

        //  Проверяем что поля заполнены
        if (personalPrice.getGoodId() <= 0) facesError("Товар должен быть выбран");
        if (personalPrice.getClientId() <= 0) facesError("Клиент должен быть выбран");
        if (personalPrice.getPrice() <= 0) facesError("Цена должна быть положительным числом");

        if (hasFacesErrors()) return;

        //
        //  Вторичная валидация
        //

        //  Дата начала раньше даты окончания
        if ((personalPrice.getBegDate() != null) && (personalPrice.getEndDate() != null) && (personalPrice.getEndDate().before(personalPrice.getBegDate()))) {
            facesError("Дата начала должна быть раньше даты окончания");
        }

        //  Единственное условие - они не должны пересекаться с введенным интервалом
        long L1 = personalPrice.getBegDate() != null ? personalPrice.getBegDate().getTime() : 0;
        long L2 = personalPrice.getEndDate() != null ? personalPrice.getEndDate().getTime() : Long.MAX_VALUE;

        PersonalPricesFilter filter = new PersonalPricesFilter();
        filter.setClientId(personalPrice.getClientId());
        filter.setGoodId(personalPrice.getGoodId());
        filter.setDate(null);

        //  Получаем персональные цены ЭТОГО клиента на ЭТОТ товар
        List<PersonalPrice> personalPrices = gf().getPersonalPrices(filter);

        for (PersonalPrice pp : personalPrices) {
            long l1 = pp.getBegDate() != null ? pp.getBegDate().getTime() : 0;
            long l2 = pp.getEndDate() != null ? pp.getEndDate().getTime() : Long.MAX_VALUE;

            if (Utils.intersecs(L1, L2, l1, l2)) {
                facesError("Даты действия персональной цены пересекаются с уже установленными персональными ценами");
                break;
            }
        }

        //
        //  Проверяем, что цена не ниже минимальной для этого товара
        //

        Good good = gf().getGoodById(personalPrice.getGoodId());
        float minPrice = good.getMinPrice();

        if (personalPrice.getPrice() < minPrice)
            facesError("Нельзя установить персональную цену ниже минимальной. Мин. цена: " + good.getMinPrice() + "р.");

        if (hasFacesErrors()) return;

        try {
            gf().savePersonalPrice(personalPrice);
            facesInfo("Персональная цена сохранена");

            //  Протоколируем создание персональной цены
            Client client = gf().getClientById(personalPrice.getClientId());
            getAuditService().logPersonalPriceCreation(getAuditHelper().getIp(),
                    getAuditHelper().getLogin(),
                    personalPrice.getGoodId(),
                    good.getCode(),
                    good.getName(),
                    personalPrice.getClientId(),
                    client.getCompanyName(),
                    (float) personalPrice.getPrice(),
                    personalPrice.getBegDate(),
                    personalPrice.getEndDate());
        } catch (ValidationException ex) {
            logger.error(ex);
            ex.printStackTrace();
            ValidationResult validationResult = ex.getResult();
            for (ValidationError error : validationResult.getErrors())
                facesError(error.toHumanString());
        } catch (CreateFailureException ex) {
            logger.error(ex);
            ex.printStackTrace();
            CreateCheckResult createCheckResult = ex.getResult();
            facesError(createCheckResult.getMsg());
        }
    }

    /**
     * Удаление данных о товаре
     */
    public void removePersonalPrice() {
        try {
            PersonalPrice oldPersonalPrice = gf().getPersonalPriceById(personalPrice.getId());

            gf().removePersonalPrice(personalPrice.getId());

            //  Протоколируем удаление персональной цены
            {
                Client client = gf().getClientById(oldPersonalPrice.getClientId());
                Good good = gf().getGoodById(oldPersonalPrice.getGoodId());

                getAuditService().logPersonalPriceDeletion(getAuditHelper().getIp(),
                        getAuditHelper().getLogin(),
                        good.getId(),
                        good.getCode(),
                        good.getName(),
                        client.getId(),
                        client.getCompanyName(),
                        (float) oldPersonalPrice.getPrice(),
                        oldPersonalPrice.getBegDate(),
                        oldPersonalPrice.getEndDate());
            }
        } catch (RemoveFailureException ex) {
            logger.error(ex);
            ex.printStackTrace();
            String[] objs = ex.getRemoveCheckResult().getLinkedObjects();
            for (String obj : objs) {
                facesError("Персональную цену нельзя удалить, т.к. он связан с другими объектами.");
                break;
            }
        }
    }

    /**
     * Обновление
     */
    public void resetAddPanel() {
        personalPrice = new PersonalPrice();
        createPriceClientNameFilter = "";
        createPriceGoodCodeFilter = "";
        createPriceGoodTypeFilter = -1;
    }


    /**
     * -------------------------------------------------------------------------
     * ----------Валидаторы----------
     * -------------------------------------------------------------------------
     */

    /**
     * Проверка: выбран ли заменяемый менеджер
     */
    public void validateClientId(FacesContext context, UIComponent component, Object obj) {
        try {
            long value = (Long) obj;
            if (value >= 0) return;
        } catch (Exception e) {
            e.printStackTrace();
        }

        facesError(context, component, "Не выбран клиент");
    }

    /**
     * Проверка: выбран ли заменяющий менеджер
     */
    public void validateGoodId(FacesContext context, UIComponent component, Object obj) {
        try {
            long value = (Long) obj;
            if (value >= 0) return;
        } catch (Exception e) {
            e.printStackTrace();
        }

        facesError(context, component, "Не выбран товар");
    }

    /**
     * Проверка: положительное целое число
     */
    public void validatePriceInteger(FacesContext context, UIComponent component, Object obj) {
        try {
            double value = (Double) obj;
            if (value > 0) return;
        } catch (Exception e) {
            e.printStackTrace();
        }

        facesError(context, component, "Цена должна быть положительным числом");
    }


    /**
     * -------------------------------------------------------------------------
     * ----------Хуй знает что за методы----------
     * -------------------------------------------------------------------------
     */

    public void setCreatePriceGoodTypeFilter(long createPriceGoodTypeFilter) {
        this.createPriceGoodTypeFilter = createPriceGoodTypeFilter;
    }

    public long getCreatePriceGoodTypeFilter() {
        return createPriceGoodTypeFilter;
    }

    public void setCreatePriceGoodCodeFilter(String createPriceGoodCodeFilter) {
        this.createPriceGoodCodeFilter = createPriceGoodCodeFilter;
    }

    public String getCreatePriceGoodCodeFilter() {
        return createPriceGoodCodeFilter;
    }

    public void setCreatePriceClientNameFilter(String createPriceClientNameFilter) {
        this.createPriceClientNameFilter = createPriceClientNameFilter;
    }

    public String getCreatePriceClientNameFilter() {
        return createPriceClientNameFilter;
    }
}
